/*
 * Copyright [2020-2030] [https://www.stylefeng.cn]
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Guns采用APACHE LICENSE 2.0开源协议，您在使用过程中，需要注意以下几点：
 *
 * 1.请不要删除和修改根目录下的LICENSE文件。
 * 2.请不要删除和修改Guns源码头部的版权声明。
 * 3.请保留源码和相关描述文件的项目出处，作者声明等。
 * 4.分发源码时候，请注明软件出处 https://gitee.com/stylefeng/guns
 * 5.在修改包名，模块名称，项目代码等时，请注明软件出处 https://gitee.com/stylefeng/guns
 * 6.若您的项目无法满足以上几点，可申请商业授权
 */
package cn.stylefeng.roses.kernel.auth.aop;

import cn.hutool.core.codec.Base64;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.stylefeng.roses.kernel.apiauth.api.constants.ApiAuthConstants;
import cn.stylefeng.roses.kernel.apiauth.api.exception.enums.ApiAuthExceptionEnum;
import cn.stylefeng.roses.kernel.jwt.JwtTokenOperator;
import cn.stylefeng.roses.kernel.jwt.api.pojo.config.JwtConfig;
import cn.stylefeng.roses.kernel.manage.service.ApiClientAuthService;
import cn.stylefeng.roses.kernel.manage.service.ApiClientService;
import cn.stylefeng.roses.kernel.rule.exception.base.ServiceException;
import cn.stylefeng.roses.kernel.rule.util.HttpServletUtil;
import cn.stylefeng.roses.kernel.scanner.api.pojo.resource.ResourceDefinition;
import cn.stylefeng.roses.kernel.scanner.api.pojo.resource.ResourceUrlParam;
import cn.stylefeng.roses.kernel.sys.api.ResourceServiceApi;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;

/**
 * API认证的AOP拦截
 *
 * @author fengshuonan
 * @since 2023/10/26 14:18
 */
@Aspect
@Slf4j
public class ApiAuthAop {

    @Resource
    private ApiClientService apiClientService;

    @Resource
    private ResourceServiceApi resourceServiceApi;

    @Resource
    private ApiClientAuthService apiClientAuthService;

    /**
     * 针对注解进行切面逻辑执行
     *
     * @author fengshuonan
     * @since 2023/10/26 14:18
     */
    @Pointcut(value = "@annotation(cn.stylefeng.roses.kernel.apiauth.api.annotations.ApiAuth)")
    public void cutService() {
    }

    @Around("cutService()")
    public Object aroundPost(ProceedingJoinPoint joinPoint) throws Throwable {

        // 执行权限校验
        this.authExecute(joinPoint);

        // 执行真正业务逻辑
        return joinPoint.proceed();

    }

    private void authExecute(ProceedingJoinPoint joinPoint) {

        // 1. 获取当前Http请求的信息
        HttpServletRequest request = HttpServletUtil.getRequest();

        // 2. 获取当前请求的路径，过滤掉多个/的问题
        String requestURI = request.getRequestURI();
        requestURI = requestURI.replaceAll("/+", "/");

        // 3. 获取当前API客户端的Token值，可能为null，读取Header中的值
        String token = request.getHeader(ApiAuthConstants.API_AUTH_HEADER_NAME);
        if (StrUtil.isEmpty(token)) {
            throw new ServiceException(ApiAuthExceptionEnum.TOKEN_IS_EMPTY);
        }

        // 4. 获取token是否为正常的JWT格式（带两个英文点）
        String[] split = token.split("\\.");
        if (split.length != 3) {
            throw new ServiceException(ApiAuthExceptionEnum.API_AUTH_TOKEN_ERROR);
        }

        // 5. 获取token的payload部分
        String payloadString = split[1];

        // base64解码，获取客户端id
        String payloadJson = Base64.decodeStr(payloadString);

        JSONObject payloadObject = null;
        try {
            payloadObject = JSON.parseObject(payloadJson);
        } catch (Exception e) {
            throw new ServiceException(ApiAuthExceptionEnum.API_AUTH_TOKEN_ERROR);
        }

        // 获取payload中的clientId
        Long clientId = payloadObject.getLong(ApiAuthConstants.API_AUTH_PAYLOAD_CLIENT_ID_FIELD_NAME);
        if (clientId == null) {
            throw new ServiceException(ApiAuthExceptionEnum.PAYLOAD_CLIENT_ID_CANT_GET);
        }

        // 获取客户端秘钥
        String apiClientSecret = apiClientService.getApiClientSecret(clientId);
        if (ObjectUtil.isEmpty(apiClientSecret)) {
            throw new ServiceException(ApiAuthExceptionEnum.PAYLOAD_CLIENT_ID_CANT_GET);
        }

        // 先校验token是否过期
        JwtConfig jwtConfig = new JwtConfig();
        jwtConfig.setJwtSecret(apiClientSecret);
        JwtTokenOperator jwtTokenOperator = new JwtTokenOperator(jwtConfig);
        boolean expiredFlag = jwtTokenOperator.validateTokenIsExpired(token);
        if (expiredFlag) {
            throw new ServiceException(ApiAuthExceptionEnum.JWT_TOKEN_EXPIRED);
        }

        // 校验token是否正确
        boolean tokenRight = jwtTokenOperator.validateToken(token);
        if (!tokenRight) {
            throw new ServiceException(ApiAuthExceptionEnum.JWT_TOKEN_ERROR);
        }

        // 获取ResourceDefinition，可能为null
        ResourceUrlParam resourceUrlParam = new ResourceUrlParam();
        resourceUrlParam.setUrl(requestURI);
        ResourceDefinition resourceDefinition = resourceServiceApi.getResourceByUrl(resourceUrlParam);

        if (resourceDefinition == null) {
            throw new ServiceException(ApiAuthExceptionEnum.URL_ERROR_RES_NOT_FOUND);
        }

        // 获取资源的code编码
        String resourceCode = resourceDefinition.getResourceCode();

        // 获取当前客户端，是否有访问该资源的权限
        boolean clientAuthRight = apiClientAuthService.validateClientAuth(clientId, resourceCode);
        if (!clientAuthRight) {
            throw new ServiceException(ApiAuthExceptionEnum.API_DONT_HAVE_AUTH);
        }

    }

}
